﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using RayDen.Library.Core;

namespace RayDen.Library.Data.Imaging
{

    public abstract class Filter
    {
        public Filter(float xw, float yw)
        {
            xWidth = (xw);
            yWidth = (yw);
            invXWidth = (1f / xw);
            invYWidth = (1f / yw);
        }


        public abstract float Evaluate(float x, float y);

        // Filter Public Data
        public float xWidth, yWidth;
        public float invXWidth, invYWidth;
    };

    public class GaussianFilter : Filter
    {
        public GaussianFilter(float xw = 2f, float yw = 2f, float a = 2f)
            : base(xw, yw)
        {
            alpha = a;
            expX = (float)Math.Exp(-alpha * xWidth * xWidth);
            expY = (float)Math.Exp(-alpha * yWidth * yWidth);
        }


        public override sealed float Evaluate(float x, float y)
        {
            return Gaussian(x, expX) * Gaussian(y, expY);
        }


        // GaussianFilter Private Data
        float alpha;
        float expX, expY;

        // GaussianFilter Utility Functions
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        float Gaussian(float d, float expv)
        {
            return Math.Max(0f, (float)Math.Exp(-alpha * d * d) - expv);
        }
    };

    public class BoxFilter : Filter
    {
        public BoxFilter(float xw, float yw)
            : base(xw, yw)
        {
        }

        public override sealed float Evaluate(float x, float y)
        {
            return 1f;
        }
    }


    public class MitchelFilter : Filter
    {
        public MitchelFilter(float xw, float yw,float b = 1f / 3f,  float c = 1f / 3f)
            : base(xw, yw)
        {
            this.B = b;
            this.C = c;
        }

        public override sealed float Evaluate(float x, float y)
        {
            float distance = MathLab.Sqrt(x * x * invXWidth * invXWidth + y * y * invYWidth * invYWidth);

            return Mitchell1D(distance);
 
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        float Mitchell1D(float x)
        {
            if (x >= 1f)
                return 0f;
            x = Math.Abs(2f * x);
            if (x > 1f)
                return (((-B / 6f - C) * x + (B + 5f * C)) * x +
                    (-2f * B - 8f * C)) * x + (4f / 3f * B + 4f * C);
            else
                return ((2f - 1.5f * B - C) * x +
                    (-3f + 2f * B + C)) * x * x +
                    (1f - B / 3f);
        }

        float B, C;
    }

    public class FilterLUT
    {
        public FilterLUT(Filter filter, float offsetX, float offsetY)
        {
            int x0 = MathLab.Ceil2UInt(offsetX - filter.xWidth);
            int x1 = MathLab.Floor2UInt(offsetX + filter.xWidth);
            int y0 = MathLab.Ceil2UInt(offsetY - filter.yWidth);
            int y1 = MathLab.Floor2UInt(offsetY + filter.yWidth);
            lutWidth = x1 - x0 + 1;
            lutHeight = y1 - y0 + 1;
            lut = new float[lutWidth * lutHeight];

            float filterNorm = 0f;
            int index = 0;
            for (int iy = y0; iy <= y1; ++iy)
            {
                for (int ix = x0; ix <= x1; ++ix)
                {
                    float filterVal = filter.Evaluate(Math.Abs(ix - offsetX), Math.Abs(iy - offsetY));
                    filterNorm += filterVal;
                    lut[index++] = filterVal;
                }
            }

            // Normalize LUT
            filterNorm = 1f / filterNorm;
            index = 0;
            for (int iy = y0; iy <= y1; ++iy)
            {
                for (int ix = x0; ix <= x1; ++ix)
                    lut[index++] *= filterNorm;
            }
        }


        public int GetWidth()
        {
            return lutWidth;
        }
        public int GetHeight()
        {
            return lutHeight;
        }

        public float[] GetLUT()
        {
            return lut;
        }


        int lutWidth, lutHeight;
        float[] lut;
    };


    public class FilterLUTs
    {
        public FilterLUTs(Filter filter, int size)
        {
            lutsSize = size + 1;
            step = 1f / (float)(size);

            luts = new FilterLUT[lutsSize * lutsSize];

            for (var iy = 0; iy < lutsSize; ++iy)
            {
                for (var ix = 0; ix < lutsSize; ++ix)
                {
                    float x = ix * step - 0.5f + step / 2f;
                    float y = iy * step - 0.5f + step / 2f;

                    luts[ix + iy * lutsSize] = new FilterLUT(filter, x, y);
                    /*std::cout << "===============================================\n";
                    std::cout << ix << "," << iy << "\n";
                    std::cout << x << "," << y << "\n";
                    std::cout << *luts[ix + iy * lutsSize] << "\n";
                    std::cout << "===============================================\n";*/
                }
            }
        }


        public FilterLUT GetLUT(float x, float y)
        {
            int ix = Math.Max(0, Math.Min(MathLab.Floor2UInt(lutsSize * (x + 0.5f)), lutsSize - 1));
            int iy = Math.Max(0, Math.Min(MathLab.Floor2UInt(lutsSize * (y + 0.5f)), lutsSize - 1));
            return luts[ix + iy * lutsSize];
        }

        private
            int lutsSize;
        float step;
        FilterLUT[] luts;
    };
}
